package com.ncr.travel.tas.configuration.model;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Index;
import org.hibernate.type.SerializationException;
import org.hibernate.util.SerializationHelper;

import com.ncr.travel.tas.shared.configuration.ConfigurationKey;
import com.ncr.travel.tas.shared.configuration.ConfigurationProductKey;
import com.ncr.travel.tas.shared.configuration.ConfigurationValueEnum;
import com.thoughtworks.xstream.XStream;

@Entity
@Table(name = "configuration_value")
public class ConfigurationValue implements Serializable, Cloneable, ConfigurationModelObject {

	/**
	 *
	 */
	private static final long serialVersionUID = 3L;

	@Id
	@GeneratedValue
	@Column(name = "value_id")
	private long id;

	@ManyToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "node_id", insertable = true, updatable = false)
	@Index(name = "NodeValueIndex")
	private ConfigurationNode parent;

	@Column(name = "name", nullable = false)
	private String key;

	@Column(name = "object_value", length = 8000)
	private String keyValue;

	@Column(name = "object_type")
	private String objectType;

	@ManyToOne(targetEntity = ConfigurationValueTemplate.class, cascade = { CascadeType.ALL })
	@JoinColumn(name = "template_id")
	private ConfigurationValueTemplate template;

	@OneToMany(mappedBy = "parentValue", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
	protected Set<ConfigurationValue> childValues = new HashSet<ConfigurationValue>();

	@ManyToOne(targetEntity = ConfigurationValue.class, cascade = CascadeType.ALL)
	@JoinColumn(name = "value_parent_id", nullable = true)
	private ConfigurationValue parentValue;

	@OneToMany(mappedBy = "parentValue", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
	private Set<TableRow> tableRows = new HashSet<TableRow>();

	@OneToMany(mappedBy = "parentValue", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
	private Set<TableColumn> tableColumns = new HashSet<TableColumn>();

	@Column(name = "value_row")
	private int row;

	@Column(name = "value_column")
	private int column;

	@Column(name = "value_tag")
	private String tag;

	@Column(name = "value_type_id")
	private ConfigurationValueEnum configurationValueEnum = ConfigurationValueEnum.GENERIC_VALUE;

	public ConfigurationValue() {
		this.configurationValueEnum = ConfigurationValueEnum.GENERIC_VALUE;
	}

	public ConfigurationValue(Object name, Object value) {
		this.key = name.toString();
		this.setValue(value);
	}

	public ConfigurationValue(Object name, ConfigurationValueEnum type) {
		this.key = name.toString();
		this.setConfigurationValueEnum(type);
	}

	public ConfigurationValue(Object name, Object value, Object tag) {
		this.key = name.toString();
		this.setValue(value);
		this.setTag(tag);
	}

	public ConfigurationValue(Object string) {
		this.key = string.toString();
	}

	private byte[] serializeForDatabase(Object value) throws SerializationException {

		return SerializationHelper.serialize((Serializable) value);

	}

	public void setId(long id) {
		this.id = id;

	}

	public long getId() {
		return id;
	}

	public void setKeyName(String key) {
		this.key = key;
	}

	public String getKeyName() {
		return key;
	}

	final public void setTag(Object tag2) {

		if (tag2 == null) {
			return;
		}

		String xml;
		XStream xstream = new XStream();
		xml = xstream.toXML(tag2);
		this.setTagValue(xml);
	}

	public Object getTag() {
		String value = this.getTagValue();
		if (value == null) {
			return null;
		}
		XStream xstream = new XStream();
		return xstream.fromXML(this.tag);

	}

    final public void setValue(Object value) {

        // TODO: Change this back to binary serialization once config svc is
        // stable
        // FIXME: As per E. Brooks, binary content cannot be stored as the schema now supports 
        // only text content for values
        setValue(value, new XStream());

    }

    /**
     * Stores a configured value using custom serialization.
     * 
     * @param value the value to persist
     * @param serializer the serialization mechanism to use
     */
    final public void setValue(Object value, XStream serializer) {

        // TODO: Change this back to binary serialization once config svc is
        // stable
        // FIXME: As per E. Brooks, binary content cannot be stored as the schema now supports 
        // only text content for values
        if (value == null) {
            return;
        }

        // set the object type
        this.objectType = value.getClass().getCanonicalName();

        String xml;
        xml = serializer.toXML(value);
        this.setKeyValue(xml);

    }

    public Object getValue() {

        return getValue(new XStream());

    }

    /**
     * Retrieves a configured value using custom serialization.
     * 
     * @param serializer the serialization mechanism to use
     * @return the resulting value
     */
    public Object getValue(XStream serializer) {

        String value = this.getKeyValue();
        if (value == null) {
            return null;
        }
        return serializer.fromXML(this.getKeyValue());

    }

	public void setParent(ConfigurationNode parent) {
		this.parent = parent;
	}

	public ConfigurationNode getParent() {
		return parent;
	}

	public void setTemplate(ConfigurationValueTemplate template) {
		this.template = template;
	}

	public ConfigurationValueTemplate getTemplate() {
		return template;
	}

	public void addChildValue(ConfigurationValue value) {
		value.setParentValue(this);
		this.childValues.add(value);
	}

	public void setChildValues(Set<ConfigurationValue> childValues) {
		this.childValues = childValues;
		for (ConfigurationValue cv : childValues) {
			cv.setParentValue(this);
		}
	}

	public Set<ConfigurationValue> getChildValues() {
		return childValues;
	}

	public void setParentValue(ConfigurationValue parentValue) {
		this.parentValue = parentValue;
	}

	public ConfigurationValue getParentValue() {
		return parentValue;
	}

	public String getObjectType() {
		return this.objectType;
	}

	public void setObjectType(String string) {

		this.objectType = string;
	}

	private void setKeyValue(String keyValue) {
		this.keyValue = keyValue;
	}

	private String getKeyValue() {
		return keyValue;
	}

	private void setTagValue(String tagValue) {
		this.tag = tagValue;
	}

	private String getTagValue() {
		return this.tag;
	}

	public void addRow(String name) {

		int ndx = this.tableRows.size();
		this.tableRows.add(new TableRow(this, ndx, name));
	}

	public Set<TableRow> getTableRows() {
		return this.tableRows;
	}

	public Set<TableColumn> getTableColumns() {

		return this.tableColumns;
	}

	public void addColumn(String name) {

		int ndx = this.tableColumns.size();
		this.tableColumns.add(new TableColumn(this, ndx, name));

	}

	public void setLocation(int row, int column) {

		this.setRow(row);
		this.setColumn(column);

	}

	public void setRow(int row) {
		this.row = row;
	}

	public int getRow() {
		return row;
	}

	public void setColumn(int column) {
		this.column = column;
	}

	public int getColumn() {
		return column;
	}

	public void setTableRows(Set<TableRow> tableRows) {
		this.tableRows = tableRows;
	}

	public void setTableColumns(Set<TableColumn> tableColumns) {
		this.tableColumns = tableColumns;
	}

	final public void setConfigurationValueEnum(ConfigurationValueEnum configurationValueEnum) {
		this.configurationValueEnum = configurationValueEnum;
	}

	public ConfigurationValueEnum getConfigurationValueEnum() {
		return configurationValueEnum;
	}

	/**
	 * find a configuration value that is a child of this object by enum
	 * @param bagDrop
	 * @return
	 */
	public ConfigurationValue findChild(ConfigurationProductKey key) {
		return this.findChild(key.toString());
	}

	/**
	 * find a configuration value that is a child of this object by enum
	 * @param bagDrop
	 * @return
	 */
	public ConfigurationValue findChild(ConfigurationKey key) {
		return this.findChild(key.toString());
	}

	/**
	 * find a configuration value that is a child of this object by String name
	 * @param string
	 * @return
	 */
	public ConfigurationValue findChild(String string) {

		if (this.childValues == null) {
			return null;
		}

		// search our child bucket for the right value
		for (ConfigurationValue v : this.childValues) {
			if (v.getKeyName().equals(string)) {
				return v;
			}
		}

		return null;

	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((configurationValueEnum == null) ? 0 : configurationValueEnum.hashCode());
		result = prime * result + ((key == null) ? 0 : key.hashCode());
		result = prime * result + ((keyValue == null) ? 0 : keyValue.hashCode());
		result = prime * result + ((objectType == null) ? 0 : objectType.hashCode());
		result = prime * result + ((tag == null) ? 0 : tag.hashCode());

		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ConfigurationValue other = (ConfigurationValue) obj;
		if (configurationValueEnum != other.configurationValueEnum)
			return false;
		if (key == null) {
			if (other.key != null)
				return false;
		} else if (!key.equals(other.key))
			return false;
		if (keyValue == null) {
			if (other.keyValue != null)
				return false;
		} else if (!keyValue.equals(other.keyValue))
			return false;
		if (objectType == null) {
			if (other.objectType != null)
				return false;
		} else if (!objectType.equals(other.objectType))
			return false;
		if ((tag == null && other.tag == null))
			return true;
		else if (tag != null && other.tag == null)
			return false;
		else if (tag == null && other.tag != null)
			return false;
		else if (!tag.equals(other.tag))
			return false;
		else
			return true;
	}

	@Override
	public String toString() {
		return "ConfigurationValue [id=" + id + ", key=" + key + ", keyValue=" + keyValue + ", objectType=" + objectType + ", tag=" + this.tag + ", configurationValueEnum=" + configurationValueEnum + "]";
	}

}
